/////////////////////////////////////////////////////////////////////////////
// Name:        epub_utils.h
// Purpose:     Epub utilities
// Author:      Julian Smart
// Modified by:
// Created:     2008-11-18
// RCS-ID:      $Id$
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#ifndef _EPUB_UTILS_H_
#define _EPUB_UTILS_H_

#include "wx/dynarray.h"
#include "wx/variant.h"

#include "imageutils/imageblock.h"
#include "datautils/stringtable.h"
#include "xmlutils/xmlparser.h"

#include "epubutils/epub_property.h"

class ebFileLocations;

/* Allow editing of multiple attributes and multiple instances of the same element type */
#define ebUSE_COMPLEX_ATTRIBUTES 1

#if ebUSE_COMPLEX_ATTRIBUTES

WX_DECLARE_OBJARRAY(wxVariant, wxVariantArray);

/*!
 * ebElementInstance
 * A single instance of an element: consists of attribute/value pairs
 */
class ebElementInstance
{
public:

    ebElementInstance() { Init(); }

    ebElementInstance(const ebElementInstance& inst) { Copy(inst); }

    void Init();
    void Clear() { m_attributes.Clear(); }

    void Copy(const ebElementInstance& inst);
    
    void operator=(const ebElementInstance& inst) { Init(); Copy(inst); }
    bool operator==(const ebElementInstance& inst) const;

    const wxVariant& operator[](size_t i) const { return GetAttribute(i); }
    wxVariant& operator[](size_t i) { return GetAttribute(i); }

    void SetAttributeValue(const wxString& name, const wxString& value, const wxArrayString& choices = wxArrayString());
    wxVariant GetAttributeValue(const wxString& name) const;

    size_t GetCount() const { return m_attributes.GetCount(); }
    const wxVariant& GetAttribute(size_t i) const { return m_attributes.GetProperty(i)->GetVariant(); }
    wxVariant& GetAttribute(size_t i) { return m_attributes.GetProperty(i)->GetVariant(); }

    bool RemoveAttribute(size_t i);

    EpProperty* GetProperty(int i) const { return m_attributes.GetProperty(i); }

    wxVariant* FindAttribute(const wxString& name) const;
    wxVariant* FindOrCreateAttribute(const wxString& name);

    EpProperties& GetProperties() { return m_attributes; }

protected:

    // wxVariantArray m_attributes; // always has at least Value
    EpProperties m_attributes;
};

WX_DECLARE_OBJARRAY(ebElementInstance, ebElementInstanceArray);

/*!
 * ebElement
 * A named element, with one or more instances
 */
class ebElement
{
public:

    ebElement(const wxString& name = wxEmptyString) { Init(); m_name = name; }

    ebElement(const ebElement& element) { Copy(element); }

    void Init();

    /// Create at least one instance
    void InitializeInstances();

    /// Get default value (Value of first instance)
    wxString GetDefaultValue() const;

    /// Get all non-empty values
    wxArrayString GetValues() const;

    /// Get combined string value from all instances
    wxString GetCombinedValue(const wxString& separator = wxT(","), bool useAnd = true) const;

    /// Get the variant corresponding to the default value
    wxVariant* GetDefaultValueVariant();

    /// Set default value (Value of first instance)
    void SetDefaultValue(const wxString& value);

    /// Get the first attribute matching this name
    wxString GetAttributeValue(const wxString& name) const;

    /// Set the first instance's attribute
    void SetAttributeValue(const wxString& name, const wxString& value);

    void Clear() { m_instances.Clear(); }

    void Copy(const ebElement& element);
    
    void operator=(const ebElement& element) { Init(); Copy(element); }
    bool operator==(const ebElement& element) const;

    size_t GetCount() const { return m_instances.GetCount(); }
    const ebElementInstance& GetInstance(size_t i) const { return m_instances[i]; }
    ebElementInstance& GetInstance(size_t i) { return m_instances[i]; }

    void AddInstance(const ebElementInstance& instance);
    bool RemoveInstance(size_t i);

    /// Actual element name
    void SetName(const wxString& name) { m_name = name; }
    const wxString& GetName() const { return m_name; }

    /// Display name
    void SetDisplayName(const wxString& name) { m_displayName = name; }
    const wxString& GetDisplayName() const { return m_displayName; }

    /// Generate XML for this element
    wxString GenerateXML(const wxString& indent, const wxString& defaultValue = wxEmptyString) const;

protected:

    ebElementInstanceArray  m_instances;    // always has at least Value
    wxString                m_name;         // actual element name
    wxString                m_displayName;  // display name
};

WX_DEFINE_ARRAY_PTR(ebElement*, ebElementPtrArray);

#endif
    // ebUSE_COMPLEX_ATTRIBUTES


/*!
 * Basic information about a book
 */

class ebBasicInfo: public wxObject
{
public:
    ebBasicInfo();
    ebBasicInfo(const ebBasicInfo& info) { Copy(info); }

    void Copy(const ebBasicInfo& info);
    void operator=(const ebBasicInfo& info) { Copy(info); }

    wxString GetAuthor() const;
    void SetAuthor(const wxString& author);

    wxString GetTitle() const;
    void SetTitle(const wxString& value);

    wxString GetId() const;
    void SetId(const wxString& value);

    wxString GetPublisher() const;
    void SetPublisher(const wxString& value);

    wxString GetLanguage() const;
    void SetLanguage(const wxString& value);

    wxString GetDescription() const;
    void SetDescription(const wxString& value);

    wxString GetContributors() const;
    void SetContributors(const wxString& value);

    wxString GetUrl() const;
    void SetUrl(const wxString& value);

    wxString GetSubject() const;
    void SetSubject(const wxString& value);

    wxString GetDate() const;
    void SetDate(const wxString& value);

    wxString GetRights() const;
    void SetRights(const wxString& value);

    wxString GetCoverage() const;
    void SetCoverage(const wxString& value);

    wxString GetFormat() const;
    void SetFormat(const wxString& value);

    wxString GetType() const;
    void SetType(const wxString& value);

    wxString GetSource() const;
    void SetSource(const wxString& value);

    wxString GetRelation() const;
    void SetRelation(const wxString& value);

#if ebUSE_COMPLEX_ATTRIBUTES
    ebElement       m_title;
    ebElement       m_id;
    ebElement       m_language;
    ebElement       m_author;
    ebElement       m_contributors;
    ebElement       m_publisher;
    ebElement       m_url;
    ebElement       m_subject;
    ebElement       m_description;
    ebElement       m_date;
    ebElement       m_rights;
    ebElement       m_coverage;
    ebElement       m_type;
    ebElement       m_format;
    ebElement       m_source;
    ebElement       m_relation;
#else
    wxString        m_title;
    wxString        m_id;
    wxString        m_language;
    wxString        m_author;
    wxString        m_contributors;
    wxString        m_publisher;
    wxString        m_url;
    wxString        m_subject;
    wxString        m_description;
    wxString        m_date;
    wxString        m_rights;
    wxString        m_coverage;
    wxString        m_type;
    wxString        m_format;
    wxString        m_source;
    wxString        m_relation;
#endif
};

/*!
 * Epub generation options
 */

class ebEpubOptions: public wxObject
{
public:
    ebEpubOptions() { Init(); }
    ebEpubOptions(const ebEpubOptions& options) { Copy(options); }

    void Init();
    void Copy(const ebEpubOptions& options);
    void operator=(const ebEpubOptions& options) { Copy(options); }

    bool            m_generateEpub;
    bool            m_generateMobiPocket;

    bool            m_generateTitlePage; // not currently used; may be used if we eventually allow
                                         // the user to add title, author etc.
    bool            m_generateTOC;
    bool            m_generateGuide;
    bool            m_generateCoverPage;
    bool            m_generateCSS;
    bool            m_expandCoverImage;
    bool            m_allowHtmlInText;
    bool            m_takeTitleFromFirstLine;
    bool            m_useRTL;
    wxString        m_importEncoding; // For text files

    /// CSS options
    int             m_firstLineIndent; // in mm
    int             m_paragraphSpacing; // in pt

    int             m_compressionLevel;
};

/*!
 * HTML content file storage
 */

class ebHtmlFile: public wxObject
{
public:
    ebHtmlFile() { Init(); }
    ebHtmlFile(const ebHtmlFile& f) { Copy(f); }

    void Init();
    void Copy(const ebHtmlFile& f);
    void operator=(const ebHtmlFile& f) { Copy(f); }

    /// Is this an HTML file? If not, we assume it's a plain text file.
    bool IsHtmlFile() const;

    /// Is this a plain text file (extension txt)?
    bool IsTextFile() const;

    /// Return an absolute HTML file, even if it's a text file.
    wxString GetAbsoluteHtmlFile(const ebFileLocations& locations) const;

    /// Return an absolute file
    wxString GetAbsoluteFile(const ebFileLocations& locations) const;

    /// Return a relative HTML file, even if it's a text file.
    wxString GetRelativeHtmlFile(const ebFileLocations& locations) const;

    /// Return a relative file
    wxString GetRelativeFile(const ebFileLocations& locations) const;

    wxString        m_fileLocation;
    wxString        m_guideType;
    wxString        m_tocTitle;
    bool            m_showInTOC;
    int             m_level;
};

WX_DECLARE_OBJARRAY(ebHtmlFile, ebHtmlFileArray);

/*!
 * File locations
 */

class ebFileLocations: public wxObject
{
public:
    ebFileLocations();
    ebFileLocations(const ebFileLocations& info) { Copy(info); }

    void Copy(const ebFileLocations& info, bool exceptHtmlFiles = false);
    void operator=(const ebFileLocations& info) { Copy(info); }

    wxString GetAbsoluteLocation(const wxString& path) const;

    /// Find the index of the HTML filename
    int FindHtmlFileByFilename(const wxString& filename);

    wxString        m_projectFolder;
    wxString        m_workingFolder;
    wxString        m_destinationFile;  // The .epub
    wxString        m_cssFile;          // Source for custom CSS file
    wxString        m_coverImageFile;   // Source for cover image file
    ebHtmlFileArray m_htmlFiles;        // HTML content files
};

/*!
 * Generated content information. This information isn't yet stored
 * in a document (there's no place to edit it) but the class centralises the
 * specification of this information.
 */

class ebGeneratedContentInfo: public wxObject
{
public:
    ebGeneratedContentInfo() { Init(); }
    ebGeneratedContentInfo(const ebGeneratedContentInfo& content) { Copy(content); }

    void Init();
    void Copy(const ebGeneratedContentInfo& content);
    void operator=(const ebGeneratedContentInfo& content) { Copy(content); }

    wxString    m_tocFilename;
    wxString    m_tocTitle;

    wxString    m_coverPageFilename;
    wxString    m_coverPageTitle;

    wxString    m_titlePageFilename;
    wxString    m_titlePageTitle;
};


/*!
 * .epub generator
 */

#define ebEPUB_OUTPUT_ERRORS_ONLY           0
#define ebEPUB_OUTPUT_ERRORS_AND_WARNINGS   1
#define ebEPUB_OUTPUT_VERBOSE               2

class ebEpubGenerator: public wxObject
{
public:

    ebEpubGenerator() { Init(); }
    ebEpubGenerator(const ebEpubGenerator& gen) { Copy(gen); }
    ebEpubGenerator(const ebBasicInfo& info, const ebFileLocations& locations, const ebEpubOptions& options)
    { Init(); m_basicInfo = info; m_locations = locations; m_options = options; }

    void Init();
    void operator=(const ebEpubGenerator& gen) { Copy(gen); }
    void Copy(const ebEpubGenerator& gen);

    ebFileLocations& GetFileLocations() { return m_locations; }
    const ebFileLocations& GetFileLocations() const { return m_locations; }

    ebBasicInfo& GetBasicInfo() { return m_basicInfo; }
    const ebBasicInfo& GetBasicInfo() const { return m_basicInfo; }

    ebEpubOptions& GetOptions() { return m_options; }
    const ebEpubOptions& GetOptions() const { return m_options; }

    const wxString& GetMobigenCommand() const { return m_mobigenCommand; }
    wxString& GetMobigenCommand() { return m_mobigenCommand; }
    void SetMobigenCommand(const wxString& cmd) { m_mobigenCommand = cmd; }

    /// Set verbosity
    void SetVerbosity(int level) { m_verbosity = level; }
    int GetVerbosity() const { return m_verbosity; }

    /// Generate the .epub
    bool GenerateEpub();

    /// Get log
    const wxString& GetLog() const { return m_log; }

    /// Helper function
    bool DoGenerate();

    /// Write a log string
    void Log(const wxString& msg, int minLevel = ebEPUB_OUTPUT_ERRORS_ONLY);

    /// Clear log
    void ClearLog() { m_log = wxEmptyString; }

    /// Clear all
    void Clear();

    /// Do initial checks
    bool InitialChecks();

    /// Create MIME type file mimetype
    bool CreateMimeTypeFile();

    /// Create container file container.xml
    bool CreateContainerFile();

    /// Create content.opf
    bool CreateContentFile();

    /// Create NCX table of contents
    bool CreateNCXFile();

    /// Create HTML table of contents
    bool CreateHTMLTOCFile();

    /// Create title page file
    bool CreateTitlePageFile();

    /// Create cover page
    bool CreateCoverPageFile();

    /// Create CSS
    bool CreateCSSFile();

    /// Create content HTML files
    bool CreateHtmlFiles();

    /// Copy image files
    bool CopyImageFiles();

    /// Get all image files in the project folder
    bool GetImageFiles();

    /// Copy other files that might be relevant
    bool CopyOtherFiles();

    /// Create epub file
    bool CreateEpubFile();

    /// Create MobiPocket file
    bool CreateMobiPocketFile();

    /// Get the location to place content HTML files etc. in
    wxString GetContentFolder() const;

    /// Get the cover page filename (without the path)
    wxString GetCoverPageFilename() const;

    /// Append paths
    wxString AppendPaths(const wxString& path1, const wxString& path2) const;

    /// Create a zip file from actual filenames and corresponding archive names.
    bool CreateZipArchive(const wxString& zipArchiveName, const wxArrayString& filenames, const wxArrayString& archiveNames, int compressionLevel);

    /// Save all files that are actually plain text files as HTML
    bool ConvertTextFilesToHTML(int flags, wxMBConv& conv);

    /// Can we generate a cover page?
    bool CanGenerateCoverPage() const;

    /// Convert from existing e-book, copying files to the project folder
    bool ConvertFromExistingEbook(const wxString& ebookFilename, const wxString& utilityDir, wxArrayString& msgs);

    /// Set/get string table to use
    void SetStringTable(wxStringTable* table) { m_stringTable = table; }
    wxStringTable* GetStringTable() const { return m_stringTable; }

    /// Get a string from the string table, or return itself
    wxString GetStringFromTable(const wxString& name) const;

    /// Get title heading size (h1, h2...)
    static int GetTitleHeadingSize() ;

    /// Get heading string, e.g. h1
    static wxString GetHeadingString(int size);

    /// Write HTML head
    static bool WriteHTMLHead(wxTextOutputStream& textStream, const wxString& title, const wxString& cssFilename = wxEmptyString, int flags = 0);

    /// Convert from a text file to an HTML file

    #define ebHTML_CONVERSION_FIRST_LINE_IS_TITLE   0x01
    #define ebHTML_CONVERSION_ALLOW_HTML_IN_TEXT    0x02
    #define ebHTML_CONVERSION_RTL                   0x04

    static bool ConvertFromTextToHTML(const wxString& textFile, const wxString& htmlFile, wxString& title, const wxString& cssFile, int flags = ebHTML_CONVERSION_FIRST_LINE_IS_TITLE, wxMBConv& conv = wxConvUTF8);

    /// Get the title from the first line of the file
    static bool GetTextFileTitle(const wxString& textFile, wxString& title, wxMBConv& conv);

    /// Indent string according to level
    static wxString IndentString(const wxString& str, int level, int spacesPerLevel = 4);

    /// Copy with creation of directories if necessary
    static bool CopyFileWithDirCreation(const wxString& file1, const wxString& file2);

    /// Get all files under the given directory, excluding another dir, matching the given extensions.
    /// Pass extensions as ";ext1;ext2;"
    static bool GetFilesUnder(wxArrayString& files, const wxString& dir, const wxString& excludingDir, const wxString& exts);
    static bool GetFilesUnder(wxArrayString& files, const wxString& topDir, const wxString& dir, const wxString& excludingDir, const wxString& exts);

    /// Make suitable id from filename
    static wxString MakeIdFromFilename(const wxString& filename);

    /// Add standard instances and attributes for creator
    static void AddCreatorAttributes(ebElement& element, bool onlyResetChoices = false);

    /// Add standard instances and attributes for contributor
    static void AddContributorAttributes(ebElement& element, bool onlyResetChoices = false);

    /// Add standard instances and attributes for identifier
    static void AddIdentifierAttributes(ebElement& element, bool onlyResetChoices = false);

    /// Add standard instances and attributes for publisher
    static void AddPublisherAttributes(ebElement& element, bool onlyResetChoices = false);

    /// Add standard instances and attributes for date
    static void AddDateAttributes(ebElement& element, bool onlyResetChoices = false);

    /// Add standard instances and attributes for language
    static void AddLanguageAttributes(ebElement& element, bool onlyResetChoices = false);

private:
    ebFileLocations m_locations;
    ebBasicInfo     m_basicInfo;
    ebEpubOptions   m_options;

    wxString        m_log;
    int             m_verbosity;
    
    wxArrayString   m_relativeZipFiles;
    wxArrayString   m_absoluteZipFiles;

    wxArrayString   m_imageFiles;

    wxString        m_mobigenCommand;

    wxStringTable*  m_stringTable;
};

/*!
 * .epub parser
 */

class wxXmlNode;

class ebEpubParser: public wxXMLParser
{
public:

    ebEpubParser() { Init(); }
    void Init();

    /// Reads the basic information about a book
    bool ReadBasicInfo(const wxString& filename, ebBasicInfo& info, wxString& coverImageZipName);

    /// Reads advanced information about a book, including file names
    bool ReadAdvancedInfo(const wxString& filename, ebBasicInfo& info, ebFileLocations& locations, wxString& opfLocation, wxString& coverImageFilename, wxString& coverPageFilename);

    /// Reads the OPF information
    bool ReadOPF(wxInputStream& stream, ebBasicInfo& info, ebFileLocations& locations, wxString& coverImageFilename, wxString& coverPageFilename);
    bool ReadOPF(const wxString& filename, ebBasicInfo& info, ebFileLocations& locations, wxString& coverImageFilename, wxString& coverPageFilename);

    /// Extract a file from zip archive, to the given file.
    bool ExtractFileFromArchive(const wxString& archiveName, const wxString& sourceFilename, const wxString& destFilename);

    /// Create an image block from the image file in the given zip archive
    bool CreateImageBlockFromZipImage(const wxString& archiveName, const wxString& sourceFilename, wxImageBlock& imageBlock, wxImage& image);

    /// Read an element, potentially with multiple instances, together with all attributes
    bool ReadElement(ebElement& element, const wxString& elementName, wxXmlNode* node);

    /// Extracts the OPF spec to a file.
    bool ExtractOPFToFile(const wxString& epubFilename, wxString& opfFilename, wxString& opfLocationInZip);
};

// Returns the string between start and end tags if found,
// setting 'str' to the remaining part.
wxString ebFindTag(const wxString& str, const wxString& tag);
wxString ebFindTag(wxString& str, const wxString& tag);

// Find title for HTML file
wxString ebFindTitle(const wxString& htmlFilename);

/// Return a relative file
wxString ebMakeRelative(const wxString& filename, const wxString& relativeTo);

#endif
    // _EPUB_UTILS_H_
